package com.zzw.filters.httpfilters;

import com.zzw.annotation.runners.RequestResourceRunner;
import com.zzw.annotation.vo.DefaultRequestResource;
import com.zzw.common.ApiCommonResponse;
import com.zzw.ratelimter.RateLimiter;
import com.zzw.ratelimter.RedisRateLimiter;
import com.zzw.requestcontext.RequestContext;
import com.zzw.utils.IpUtils;
import com.zzw.utils.JsonHelper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

/**
 * @author zzw
 */
public class DefaultRequestResourceContextFilter extends OncePerRequestFilter implements Ordered {

    private static final Logger logger = LoggerFactory.getLogger(DefaultRequestResourceContextFilter.class);

    private static final ThreadLocal<RequestContext> FILTER_REQUEST_CONTEXT = new ThreadLocal<>();

    public DefaultRequestResourceContextFilter(RequestResourceRunner requestResourceRunner, RedisRateLimiter redisRateLimiter) {
        this.requestResourceRunner = requestResourceRunner;
        this.redisRateLimiter = redisRateLimiter;
    }

    public static RequestContext getRequestContext() {
        return FILTER_REQUEST_CONTEXT.get();
    }

    private final RequestResourceRunner requestResourceRunner;
    private final RedisRateLimiter redisRateLimiter;
    private static final String REQUEST_ID = "request-id";

    @Override
    public int getOrder() {
        return 0;
    }

    private String generateSn() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }


    @Override
    @SuppressWarnings("DuplicatedCode")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        DefaultRequestResource requestResource = requestResourceRunner.getRequestResource(request.getRequestURI());
        if (requestResource == null) {
            return;
        }
        LocalDateTime requestTime = LocalDateTime.now();
        String requestId = generateSn();
        MDC.put(REQUEST_ID, requestId);
        String ip = IpUtils.tryGetRealIp(request);
        logger.info("request context filter start，ip:{},request time:{}", ip, requestTime);
        try {
            if (!qpsLimit(request.getRequestURI(), response, requestId)) {
                return;
            }
            FILTER_REQUEST_CONTEXT.set(new RequestContext(requestId, requestTime, ip));
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            logger.error("request resource filter error", e);
        } finally {
            FILTER_REQUEST_CONTEXT.remove();
            MDC.remove(REQUEST_ID);
            logger.info("request context filter end,cost time :{}", (ChronoUnit.MILLIS.between(requestTime, LocalDateTime.now())));
        }
    }

    private boolean qpsLimit(String requestUri, HttpServletResponse response, String requestId) throws IOException {
        DefaultRequestResource requestResource = requestResourceRunner.getRequestResource(requestUri);
        if (requestResource != null) {
            RateLimiter.Response allowed = redisRateLimiter.isAllowed(StringUtils.isNotBlank(requestResource.getLimitKey()) ? requestResource.getLimitKey() : requestUri, requestResource.getQps());
            if (!allowed.isAllowed()) {
                response.setHeader("Content-Type", MediaType.APPLICATION_JSON.toString());
                response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
                response.getWriter().println(JsonHelper.toJson(ApiCommonResponse.of(requestId
                        , "" + HttpStatus.TOO_MANY_REQUESTS.value(), HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase())));
                return false;
            }
        }
        return true;
    }
}
